package com.aldobucchi.alias
{
	import flash.utils.Dictionary;
	
	import com.aldobucchi.ds.container.utils.ContainerUtil;
	import com.aldobucchi.ds.container.zet.IRSet;
	import com.aldobucchi.ds.container.zet.IWSet;
	import com.aldobucchi.ds.container.zet.impl.owSet;
	import com.aldobucchi.utils.AutoUpdateFlag;
	
	
	
	
	/**
	 * 
	 * TODO:
	 * - dispatch event when smushing/unsmushing
	 * - publish a set with cannonicals ( redirected )
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class AliasContext
	{


		private var records:Dictionary = new Dictionary( );


		internal var compareFunction:Function;

		
		private var autoUpdateFlag:AutoUpdateFlag = new AutoUpdateFlag( null, enableChangedHandler );
		
		
		
		

		public function AliasContext( compareFunction:Function )
		{
			this.compareFunction = compareFunction;
			
			
		}
		
		
		
		

		/**
		 * 
		 * Asserts that two elements are equal.
		 * 
		 * @param e1
		 * @param e2
		 * @return 
		 * 
		 */		
		public function eq( e1:*, e2:* ):Boolean
		{
			var ordered:Array = orderPair( e1, e2 );
			if ( ordered == null ) // they are equal
			{
				return false;
			}
			e1 = ordered[ 0 ];
			e2 = ordered[ 1 ];
			
			if ( ! rememberOrdered( e1, e2 ) )
			{
				return false; // already occured
			}
			
			
			eqOrdered( e1, e2 );
			
			return true;
		}
		

		
		/**
		 * 
		 * @param e1
		 * @param e2
		 * @return 
		 * 
		 */		
		private function eqOrdered( e1:*, e2:* ):void
		{
		
			// get the aliases from both
			var aliases1:IRSet = getRecord( e1 ).aliases;

			var aliases2:IRSet = getRecord( e2 ).aliases;
			
			var union:IRSet = ContainerUtil.getSetUnion( owSet( [ aliases1, aliases2  ] ) );
				

			union.walk( function( e:*, m:int ):void { 
			
					var record:AliasRecord = getRecord( e );
					
					record._aliases.autoUpdateFlag.disable( );
					
						union.walk( function( e2:*, m2:int ):void{
								record._aliases.add( e2 );
							} );
					
					record._aliases.autoUpdateFlag.enable( );
			
				} );		
		
		
		}

		


		public function neq( e1:*, e2:* ):Boolean
		{
			return neqMany( [ [ e1, e2 ] ] );
		}
		



		public function neqMany( pairs:Array ):Boolean
		{
		
			var somethingChanged:Boolean = false;
			for each ( var pair:Array in pairs )
				if ( forget( pair[0], pair[1] ) )
					somethingChanged = true;

			if ( ! somethingChanged )
				return false;


			autoUpdateFlag.disable( );				
			reset( );
			replay( );
			autoUpdateFlag.enable( );
			
			return true;		
					
		}
		
		
		
		
		
		
		private function forget( e1:*, e2:* ):Boolean
		{
			var ordered:Array = orderPair( e1, e2 );
			if ( ordered == null ) // they are equal
			{
				return false;
			}

			e1 = ordered[ 0 ];
			e2 = ordered[ 1 ];

			return forgetOrdered( e1, e2 );
		}
		
		
		
		
		
		private function replay( ):void
		{
			autoUpdateFlag.disable( );

			for ( var e1:* in memory )
			{
				for ( var e2:* in memory[ e1 ] )
				{
					eqOrdered( e1, e2 ); // we know they are already ordered 				
				}
			}			
			autoUpdateFlag.enable( );
		}
		
		


		private function reset( ):void
		{
			autoUpdateFlag.disable( );
			for each ( var record:AliasRecord in records )
				record.reset( );
			autoUpdateFlag.enable( );
		}
		
		
		
		
		
		
		private function enableChangedHandler( enabled:Boolean ):void
		{
			for each ( var record:AliasRecord in records )
			{
				var zet:IWSet = record._aliases;
				if ( enabled )
					zet.autoUpdateFlag.enable( );
				else
					zet.autoUpdateFlag.disable( );							
			}		
		}
		
		
		
		

		
		
		

		public function getRecord( e:* ):AliasRecord
		{
		
			if ( ! records[ e ] )
			{
				var r:AliasRecord = new AliasRecord( e, this );
				records[ e ] = r;
				return r; // return local copy to avoid extra hash lookup
			}
			return records[ e ] as AliasRecord;
		}
		
		
		
		
		/**
		 * 
		 * @param e1
		 * @param e2
		 * @return 
		 * 		The ordered pair or NULL if they are equal ( according to the compare function )
		 * 
		 */		
		private function orderPair( e1:*, e2:* ):Array
		{
			var result:int = compare( e1, e2 );
			if ( result == 0 )
			{
				return null;		
			}
			
			if ( result == 1 )
				return [ e2, e1 ];
		
			return [ e1, e2 ];		

		}
		
		
		
		private function compare( e1:*, e2:* ):int
		{
			return compareFunction.apply( null, [ e1, e2 ] ) as int;	
		}
		
		
		
		
		
		
		/////////////////////////////////////////////////
		// internal log
		/////////////////////////////////////////////////


		private var memory:Dictionary = new Dictionary( );

		
		/**
		 * 
		 * 
		 * 
		 * @param e1
		 * @param e2
		 * @return 
		 * 		True if this entry is created. False if it is already in memory
		 * 
		 */		
		private function rememberOrdered( e1:*, e2:* ):Boolean
		{
		
			if ( memory[ e1 ] === undefined )
				memory[ e1 ] = new Dictionary( );			
		
			if ( memory[ e1 ][ e2 ] === true )
				return false; // already logged			

			memory[ e1 ][ e2 ] = true;		
		
			return true;
		}
		
		

		private function forgetOrdered( e1:*, e2:* ):Boolean
		{
		
			if ( memory[ e1 ] === undefined )
				return false;
			
			if ( memory[ e1 ][ e2 ] === undefined )
				return false;
			
			
			delete memory[ e1 ][ e2 ];
			
			
			for ( var e:* in memory[ e1 ] ) 
				return true; // we return if there is at least one value left for e1
			 
			// otherwise we delete it ( memory ettiquette )
			
			delete memory[ e1 ];
		
			return true;
		
		}
		
		
		

	}

}